﻿using IceZero.DataStore.Models;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Context;
using NHibernate.Mapping.Attributes;
using NHibernate.Tool.hbm2ddl;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace IceZero.DataStore
{
    /// <summary>
    /// NHibernate 帮助器
    /// </summary>
    public sealed class NHibernateHelper
    {
        /// <summary>
        /// 会话工厂实例
        /// </summary>
        public static ISessionFactory SessionFactory;

        /// <summary>
        /// Nhibernate 配置对象
        /// </summary>
        public static Configuration Configuration;

        /// <summary>
        /// 架构导出工具，导出数据结构到数据库
        /// </summary>
        public static SchemaExport SchemaExport;

        /// <summary>
        /// 架构升级工具，导出数据结构到数据库
        /// </summary>
        public static SchemaUpdate SchemaUpdate;

        /// <summary>
        /// 原始sql语句查看器
        /// </summary>
        private static RawSqlWatcher rawSqlWathcher = new RawSqlWatcher();
        /// <summary>
        /// 数据库初始化标记文件路径
        /// </summary>
        private static string _dbInitLockPath = Path.Combine(AppContext.BaseDirectory, "db-init.lock");

        /// <summary>
        /// 构造函数
        /// </summary>
        static NHibernateHelper()
        {
            Configuration = new Configuration();
            NHibernateHelperConfig.ConfigByHibernateCfgXml(Configuration);
        }
        /// <summary>
        /// 配置相关对象
        /// </summary>
        public static void Configure(string configFilePath = "hibernate.cfg.xml")
        {

            //构建配置对象
            Configuration = Configuration.Configure(configFilePath);

            //构建会话工厂
            SessionFactory = Configuration.BuildSessionFactory();

            //构建架构导出工具
            SchemaExport = new SchemaExport(Configuration);

            //构建架构升级工具
            SchemaUpdate = new SchemaUpdate(Configuration);

        }

        /// <summary>
        /// 关闭会话工厂
        /// </summary>
        public static void CloseSessionFactory()
        {
            if (SessionFactory == null)
            {
                return;
            }
            SessionFactory.Close();
        }

        /// <summary>
        /// 获取一个新的会话对象，需要自己主动关闭
        /// </summary>
        /// <param name="action"></param>
        public static ISession GetNewSession() => SessionFactory.OpenSession();

        /// <summary>
        /// 关闭会话
        /// </summary>
        /// <param name="session"></param>
        public static void CloseSession(ISession session)
        {
            if (session == null)
            {
                return;
            }
            session.Close();
        }

        /// <summary>
        /// 使用会话，自动关闭会话对象
        /// </summary>
        /// <param name="action"></param>
        public static void UseSessionWatchRawSql(Action<ISession> action)
        {
            using (ISession session = SessionFactory.WithOptions().Interceptor(rawSqlWathcher).OpenSession())
            {
                try
                {
                    action(session);
                }
                catch (HibernateException ex)
                {
                    Console.WriteLine($"HibernateException:{ex.Message}");
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Exception:{ex.Message}");
                }
                finally
                {
                    /*把内存里面的数据写入到数据库*/
                    session.Flush();
                    /**/
                    session.Clear();

                    /*
                     * 关闭会话并清理掉
                     * 会话不是必须要关闭掉，但是至少需要断开 ado.net 链接
                     * session.Disconnect();
                     */
                    session.Close();
                }
            }
        }

        /// <summary>
        /// 使用会话，自动关闭会话对象
        /// </summary>
        /// <param name="action"></param>
        public static void UseSession(Action<ISession> action)
        {
            using (ISession session = SessionFactory.OpenSession())
            {
                try
                {
                    action(session);
                }
                catch (HibernateException ex)
                {
                    Console.WriteLine($"HibernateException:{ex.Message}");
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Exception:{ex.Message}");
                }
                finally
                {
                    session.Flush();
                    session.Clear();
                    session.Close();
                }
            }
        }

        /// <summary>
        /// 使用会话，自动关闭会话对象
        /// </summary>
        /// <param name="action"></param>
        public static async Task UseSessionAsync(Func<ISession, Task> action, CancellationToken cancellationToken = default)
        {
            using (ISession session = SessionFactory.OpenSession())
            {
                try
                {
                    await action(session);
                }
                catch (HibernateException ex)
                {
                    Console.WriteLine($"HibernateException:{ex.Message}");
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Exception:{ex.Message}");
                }
                finally
                {
                    await session.FlushAsync(cancellationToken);
                    session.Clear();
                    session.Close();
                }
            }
        }

        /// <summary>
        /// 使用会话，自动关闭会话对象,通过自定义链接对象调用
        /// </summary>
        /// <param name="action"></param>
        public static void UseSessionWithCustomConnection(DbConnection connection, Action<ISession> action)
        {
            using (ISession session = SessionFactory.WithOptions().Connection(connection).OpenSession())
            {
                try
                {
                    action(session);
                }
                catch (HibernateException ex)
                {
                    Console.WriteLine($"HibernateException:{ex.Message}");
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"Exception:{ex.Message}");
                }
                finally
                {
                    session.Flush();
                    session.Clear();
                    session.Close();
                }

            }
        }

        /// <summary>
        /// 通过会话使用事务，自动关闭会话对象，事务对象，事务正常会自动提交，错误会自动回滚，无需手动触发
        /// </summary>
        /// <param name="action"></param>
        public static void UseSessionWithTransaction(Action<ISession> action)
        {
            //开启会话
            using (ISession session = SessionFactory.OpenSession())
            {
                //开启事务
                using (ITransaction transaction = session.BeginTransaction())
                {
                    try
                    {
                        action(session);
                        //Commit会调用 session.Flush()
                        transaction.Commit();
                    }
                    catch (HibernateException ex)
                    {
                        Console.WriteLine($"HibernateException:{ex.Message}");
                        transaction.Rollback();
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"Exception:{ex.Message}");
                        transaction.Rollback();
                    }
                    finally
                    {
                        session.Clear();
                        session.Close();
                    }
                }
            }
        }

        /// <summary>
        /// 通过会话使用事务，自动关闭会话对象，事务对象，事务正常会自动提交，错误会自动回滚，无需手动触发
        /// </summary>
        /// <param name="action"></param>
        public static async Task UseSessionWithTransactionAsync(Func<ISession, Task> action, CancellationToken cancellationToken = default)
        {
            //开启会话
            using (ISession session = SessionFactory.OpenSession())
            {
                //开启事务
                using (ITransaction transaction = session.BeginTransaction())
                {
                    try
                    {
                        await action(session);
                        await transaction.CommitAsync(cancellationToken);
                    }
                    catch (HibernateException ex)
                    {
                        Console.WriteLine($"HibernateException:{ex.Message}");
                        await transaction.RollbackAsync(cancellationToken);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"Exception:{ex.Message}");
                        await transaction.RollbackAsync(cancellationToken);
                    }
                    finally
                    {
                        session.Clear();
                        session.Close();
                    }
                }
            }
        }

        /// <summary>
        /// 通过会话使用事务，自动关闭会话对象，事务对象，事务正常会自动提交，错误会自动回滚，无需手动触发,主要用于查看原始sql语句
        /// </summary>
        /// <param name="action"></param>
        public static void UseSessionWithTransactionWatchRawSql(Action<ISession> action)
        {
            using (ISession session = SessionFactory.WithOptions().Interceptor(rawSqlWathcher).OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    try
                    {
                        action(session);
                        transaction.Commit();
                    }
                    catch (HibernateException ex)
                    {
                        Console.WriteLine($"HibernateException:{ex.Message}");
                        transaction.Rollback();
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"Exception:{ex.Message}");
                        transaction.Rollback();
                    }
                    finally
                    {
                        session.Clear();
                        session.Close();
                    }
                }
            }
        }

        /// <summary>
        /// 通过会话使用事务,会话和事务对象自行控制
        /// </summary>
        /// <param name="action"></param>
        public static void UseSessionWithTransactionHandle(Action<ISession, ITransaction> action)
        {
            using (ISession session = SessionFactory.OpenSession())
            {
                using (ITransaction transaction = session.BeginTransaction())
                {
                    try
                    {
                        action(session, transaction);
                    }
                    catch (HibernateException ex)
                    {
                        Console.WriteLine($"HibernateException:{ex.Message}");
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine($"Exception:{ex.Message}");
                    }
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public static ISession GetCurrentSession()
        {
            return SessionFactory.GetCurrentSession();
        }

        private static void InitDB()
        {
            File.WriteAllText(_dbInitLockPath, DateTime.Now.ToString());
        }

        private static bool IsInitDB()
        {
            return File.Exists(_dbInitLockPath);
        }

        /// <summary>
        /// 使用 NHibernate 内置的 SchemaExport/SchemaUpdate 工具构建架构，
        /// 如果未初始化就初始化，如果初始化了就升级
        /// </summary>
        /// <param name="rebuild">是否重新构建架构</param>
        public static void AutoBuildUpdateTable(bool rebuild = false)
        {
            if (rebuild)
            {
                BuildTable();
                return;
            }

            if (IsInitDB())
            {
                UpdateTable();
            }
            else
            {
                BuildTable();
            }
        }

        /// <summary>
        /// 使用 NHibernate 内置的 SchemaExport 工具创建架构
        /// </summary>
        public static void BuildTable()
        {
            InitDB();
            //SchemaExport.SetOutputFile("gen-db.sql");
            SchemaExport.Execute(
                useStdOut: true,
                execute: true,
                justDrop: false);

        }
        /// <summary>
        /// 使用 NHibernate 内置的 SchemaExport 工具升级架构
        /// </summary>
        public static void UpdateTable()
        {
            if (!IsInitDB())
            {
                return;
            }
            SchemaUpdate.Execute(
                useStdOut: true,
                doUpdate: true
                );
        }

        /// <summary>
        /// 
        /// </summary>
        public static void BindCurrentSessionContext()
        {
            if (!CurrentSessionContext.HasBind(SessionFactory))
            {
                // 调试sql,放开这句，注释下面一条
#if DEBUG
                CurrentSessionContext.Bind(SessionFactory.WithOptions().Interceptor(rawSqlWathcher).OpenSession());
#else
                CurrentSessionContext.Bind(SessionFactory.OpenSession());
#endif
            }
        }

        /// <summary>
        /// 生成映射文件
        /// </summary>
        /// <param name="genFile">是否生成xml文件，如果是，会生成在程序运行目录下的 MappingXMLs 文件夹下</param>
        /// <returns></returns>
        public static List<Stream> GenMappingXMLs(bool genFile = false)
        {
            var fileStreams = new List<Stream>();

            var assemblyFiles = Directory.GetFiles(AppContext.BaseDirectory, "*.DataStore.Models.dll").ToList();
            //获取程序启动时所有程序集
            var assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
            // 加载所有DataStore.Models 结尾的程序集
            assemblyFiles.ForEach(assemblyFile =>
            {
                var file = new FileInfo(assemblyFile);
                var fileName = Path.GetFileNameWithoutExtension(file.Name);
                var isExist = assemblies.Exists(assembly =>
                 {
                     var existFileName = assembly.GetName().Name;
                     return existFileName == fileName;
                 });
                //只加载还不存在的数据集
                if (!isExist)
                {
                    Assembly.LoadFile(assemblyFile);
                }
            });
            //找到程序集里面含有指定接口（IIceZeroAssemblyInfo）实现的
            var locateInterfaceName = typeof(IIceZeroAssemblyInfo).FullName;
            //重新加载程序集列表
            assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
            assemblies = assemblies.Where(a => a.GetName().Name.EndsWith("DataStore.Models")).ToList();
            assemblies = assemblies.Where(assembly =>
                    {
                        //获取程序集里面的所有类型
                        var types = assembly.GetTypes();
                        //返回任意类型实现的接口中有包含 IIceZeroAssemblyInfo 的类，比较通过类名去比较，
                        //如果用 type 实例比较会查找不到，因为不同的实例是不相等的
                        return types.Any(t => t.GetInterfaces().Select(i => i.FullName).Contains(locateInterfaceName));
                    }).ToList();
            var hbmSerializer = HbmSerializer.Default;
            hbmSerializer.Validate = true;
            hbmSerializer.WriteDateComment = true;
            foreach (var assembly in assemblies)
            {
                try
                {
                    var fileStream = new MemoryStream();
                    hbmSerializer.Serialize(fileStream, assembly);
                    if (genFile)
                    {
                        var basePath = Path.Combine(AppContext.BaseDirectory, "MappingXMLs");
                        Directory.CreateDirectory(basePath);
                        using (var fs = File.OpenWrite(Path.Combine(basePath, assembly.GetName().Name + ".hbm.xml")))
                        {
                            fileStream.Seek(0, SeekOrigin.Begin);
                            fileStream.CopyTo(fs);
                        }
                        fileStream.Seek(0, SeekOrigin.Begin);

                    }
                    fileStreams.Add(fileStream);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                }
            }
            return fileStreams;
        }
    }
}
